#region Using

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Collections.ObjectModel;

#endregion

namespace SemanticEngine.Foaf
{
	public static class FoafWriter
	{

		private static Dictionary<string, string> xmlNamespaces;
		private static Dictionary<string, string> SupportedNamespaces
		{
			get
			{
				if (xmlNamespaces == null)
				{
					xmlNamespaces = new Dictionary<string, string>();

					//xmlNamespaces.Add("foaf", "http://xmlns.com/foaf/0.1/");
					xmlNamespaces.Add("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
					xmlNamespaces.Add("geo", "http://www.w3.org/2003/01/geo/wgs84_pos#");
					xmlNamespaces.Add("dc", "http://purl.org/dc/elements/1.1");
				}

				return xmlNamespaces;
			}
		}

		public static void WriteXml(Stream stream, Person person)
		{
			XmlWriter writer = GetWriter(stream);

			WriteProfileDocument(writer);
			WriteFoafPerson(writer, person);

			CloseWriter(writer);
		}

		/// <summary>
		/// Creates the necessary startup information for the FOAF document
		/// </summary>
		/// <param name="stream"></param>
		/// <returns></returns>
		private static XmlWriter GetWriter(Stream stream)
		{
			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Encoding = Encoding.UTF8;
			settings.Indent = true;
			XmlWriter xmlWriter = XmlWriter.Create(stream, settings);

			xmlWriter.WriteStartDocument();
			xmlWriter.WriteStartElement("rdf", "RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
			xmlWriter.WriteAttributeString("xmlns", "http://xmlns.com/foaf/0.1/");

			foreach (string prefix in SupportedNamespaces.Keys)
			{
				xmlWriter.WriteAttributeString("xmlns", prefix, null, SupportedNamespaces[prefix]);
			}

			return xmlWriter;
		}

		/// <summary>
		/// Closes up the FOAF document
		/// </summary>
		/// <param name="xmlWriter"></param>
		private static void CloseWriter(XmlWriter xmlWriter)
		{
			xmlWriter.WriteEndElement(); // rdf:RDF
			xmlWriter.WriteEndDocument();
			xmlWriter.Close();
		}

		private static void WriteProfileDocument(XmlWriter writer)
		{
			writer.WriteStartElement("PersonalProfileDocument");
			writer.WriteAttributeString("rdf", "about", null, "");

			writer.WriteStartElement("maker");
			writer.WriteAttributeString("rdf", "resource", null, "#me");
			writer.WriteEndElement(); // foaf:maker

			writer.WriteStartElement("primaryTopic");
			writer.WriteAttributeString("rdf", "resource", null, "#me");
			writer.WriteEndElement(); // foaf:primaryTopic

			writer.WriteEndElement();  // foaf:PersonalProfileDocument
		}

		/// <summary>
		/// Write a FOAF:Person and any friends to the output stream
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="person"></param>
		private static void WriteFoafPerson(XmlWriter writer, Person person)
		{
			writer.WriteStartElement("Person");

			if (!string.IsNullOrEmpty(person.SeeAlso))
				writer.WriteAttributeString("rdf", "resource", null, person.SeeAlso);
			
			WriteStringValue(writer, "name", person.Name);
			WriteStringValue(writer, "title", person.Title);
			WriteStringValue(writer, "givenname", person.Firstname);
			WriteStringValue(writer, "family_name", person.Lastname);
			WriteStringValue(writer, "nick", person.NickName);
			WriteStringValue(writer, "phone", !string.IsNullOrEmpty(person.Phone) ? "tel:" + person.Phone : null);

			if (person.Birthday != DateTime.MinValue)
			{
				WriteStringValue(writer, "birthday", person.Birthday.ToString("yyyy-MM-dd"));
			}			

			foreach (string email in person.Emails)
			{
				WriteStringValue(writer, "mbox_sha1sum", CalculateSHA1(email, Encoding.UTF8));
			}

			foreach (Uri openid in person.OpenIDs)
			{
				WriteStringValue(writer, "openid", openid.ToString());
			}

			foreach (Uri homepage in person.Homepages)
			{
				writer.WriteStartElement("homepage");
				writer.WriteAttributeString("rdf", "resource", null, homepage.ToString());
				writer.WriteEndElement();
			}

			if (!string.IsNullOrEmpty(person.Blog))
			{
				writer.WriteStartElement( "weblog");
				writer.WriteAttributeString("rdf", "resource", null, person.Blog);
				writer.WriteEndElement();
			}

			if (!string.IsNullOrEmpty(person.SeeAlso))// && person.Rdf != HttpContext.Current.Request.Url.ToString())
			{
				writer.WriteStartElement("rdfs", "seeAlso", null);
				writer.WriteAttributeString("rdf", "resource", null, person.SeeAlso);
				writer.WriteEndElement();
			}

			if (person.PhotoUrl != null)
			{
				writer.WriteStartElement("depiction");
				writer.WriteAttributeString("rdf", "resource", null, person.PhotoUrl.ToString());
				writer.WriteEndElement();
			}

			if (person.GeoTag != null)
			{
				writer.WriteStartElement("based_near");
				writer.WriteStartElement("geo", "Point", null);
				writer.WriteAttributeString("geo", "long", null, person.GeoTag.Longitue.ToString());
				writer.WriteAttributeString("geo", "lat", null, person.GeoTag.Latitude.ToString());
				writer.WriteEndElement();
				writer.WriteEndElement();
			}

			WriteOnlineAccounts(writer, person.OnlineAccounts);

			if (person.Friends != null && person.Friends.Count > 0)
			{
				foreach (Person friend in person.Friends)
				{
					writer.WriteStartElement("knows");
					WriteFoafPerson(writer, friend);
					writer.WriteEndElement();  // foaf:knows
				}
			}

			writer.WriteEndElement(); // foaf:Person
		}

		private static void WriteStringValue(XmlWriter writer, string localName, string value)
		{
			if (!string.IsNullOrEmpty(value))
			{
				writer.WriteElementString(localName, value);
			}
		}

		private static void WriteOnlineAccounts(XmlWriter writer, IList<OnlineAccount> accounts)
		{
			if (accounts.Count > 0)
			{
				writer.WriteStartElement("holdsAccount");

				foreach (OnlineAccount account in accounts)
				{
					writer.WriteStartElement("OnlineAccount");

					writer.WriteStartElement("accountProfilePage");
					writer.WriteAttributeString("rdf", "resource", null, account.ProfilePageUrl.ToString());
					writer.WriteAttributeString("dc", "title", null, account.Name);
					writer.WriteEndElement(); // rdf:accountServiceHomepage

					writer.WriteEndElement(); // foaf:OnlineAccount
				}

				writer.WriteEndElement(); // foaf:holdsAccount
			}
		}

		private static string CalculateSHA1(string text, Encoding enc)
		{
			byte[] buffer = enc.GetBytes(text);
			SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
			string hash = BitConverter.ToString(sha1.ComputeHash(buffer));

			return hash.ToLowerInvariant().Replace("-", "");
		}


	}
}
